package com.jlf.core.engine.builder;

import com.jlf.core.engine.domain.SchemaDomainDO;
import com.jlf.core.engine.domain.SchemaFieldDO;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.objectweb.asm.*;

import java.io.File;
import java.io.FileOutputStream;
import java.util.List;


/**
 * 模型领域的自动构建
 *
 * @author wujr
 * 2023/7/8
 * @version 1.0
 * 【变更历史】
 * [wujr 2023/7/8 1.0 新增]
 */
@Slf4j
@Getter
public class AutoDomainBuilder {
    private final boolean saveDomainClass = false;
    private final AutoClassLoader engineClassLoader;
    private final SchemaDomainDO schemaDomain;
    private final List<SchemaFieldDO> schemaFields;
    private final String domainSimpleCode;
    private Class<?> domainClass;
    private String asmDomainInternal;
    private String domainType;
    private String basePackage;

    public AutoDomainBuilder(AutoClassLoader engineClassLoader, SchemaDomainDO schemaDomain, List<SchemaFieldDO> schemaFields){
        this.engineClassLoader = engineClassLoader;
        this.schemaDomain = schemaDomain;
        this.schemaFields = schemaFields;
        this.domainSimpleCode = StringUtils.capitalize(schemaDomain.getDomainCode());
    }

    private void parseBasePackage(){
        String domainPackage = domainClass.getPackage().getName();
        if (domainPackage.endsWith(".domain")){
            basePackage = domainPackage.substring(0, domainPackage.length() - ".domain".length());
        }else{
            basePackage = domainPackage;
        }
    }
    /**
     * 动态创建模型领域类
     * 本方法采用asm
     * @return 模型领域的类
     * @throws Exception 创建失败，抛出异常
     */
    public Class<?> generate() throws Exception{
        String domainSimpleName = schemaDomain.simpleName();
        String domainClass = schemaDomain.getDomainPackage() + "." + domainSimpleName;
        try{
            this.domainClass = engineClassLoader.loadClass(domainClass);
            this.asmDomainInternal = Type.getInternalName(this.domainClass);
            this.domainType = Type.getDescriptor(this.domainClass);
            parseBasePackage();
            return this.domainClass;
        }catch (Exception e){
            log.trace("模型领域：{} 的类不存在，需要动态创建！", domainClass);
        }
        this.asmDomainInternal = AutoBuilderTool.class2Asm(domainClass);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        String signature;
        if (schemaDomain.isLockOptimistic()){
            // 该模型需要乐观锁
            signature = AutoBuilderTool.asmVersionSimpleSignature("L" + asmDomainInternal + ";");
        }else{
            signature = AutoBuilderTool.asmSimpleSignature("L" + asmDomainInternal + ";");
        }
        // 第一个参数 V1_8 是生成的class的版本号， 对应class文件中的主版本号和次版本号， 即minor_version和major_version
        // 第二个参数 ACC_PUBLIC 表示该类的访问标识。这是一个public的类。 对应class文件中的access_flags
        // 第三个参数是生成的类的类名。 需要注意，这里是类的全限定名。 如果生成的class带有包名， 如com.jg.zhang.Example， 那么这里传入的参数必须是com/jg/zhang/Example  。对应class文件中的this_class
        // 第四个参数是和泛型相关的， 这里我们不关新， 传入null表示这不是一个泛型类。这个参数对应class文件中的Signature属性（attribute）
        // 第五个参数是当前类的父类的全限定名。 没有的话默认应该继承Object。 这个参数对应class文件中的super_class
        // 第六个参数是String[]类型的， 传入当前要生成的类的直接实现的接口。 这里这个类没实现任何接口， 所以传入null 。 这个参数对应class文件中的interfaces
        cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, asmDomainInternal, signature, AutoBuilderTool._asmSimpleInternal, null);
        // 添加注解：@Domain
        AnnotationVisitor av = cw.visitAnnotation(AutoBuilderTool._asmDomainType, true);
        // @Domain的参数
        av.visit("code", schemaDomain.getDomainCode());
        av.visit("name", schemaDomain.getDomainName());
        av.visit("table", schemaDomain.getDomainTable());
        av.visit("version", schemaDomain.getDomainVersion());
        av.visitEnd();
        // 默认的构造函数
        AutoBuilderTool.asmDefaultConstructor(cw);
        // 添加版本号
        AutoBuilderTool.asmAddVersion(cw, schemaDomain.getDomainVersion());
        // 添加成员变量
        for (SchemaFieldDO schemaField : schemaFields){
            AutoBuilderTool.asmAddField(cw, asmDomainInternal, schemaField);
        }
        if (schemaDomain.isLockOptimistic()){
            // 添加乐观锁的版本
            AutoBuilderTool.asmAddOptimisticVersion(cw, asmDomainInternal);
        }
        cw.visitEnd();
        byte[] code = cw.toByteArray();
        if (saveDomainClass) {
            String tempPath = System.getProperty("java.io.tmpdir") + File.separator;
            FileOutputStream fos = new FileOutputStream(tempPath + domainSimpleName + ".class");
            fos.write(code);
            fos.close();
        }
        this.domainClass = engineClassLoader.defineClass(domainClass, code);
        this.domainType = Type.getDescriptor(this.domainClass);
        parseBasePackage();
        return this.domainClass;
    }
}
